package com.hc.testaudio.util;

import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.util.Log;

import com.yc.yclibx.YcManage;
import com.yc.yclibx.comment.YcLog;
import com.yc.yclibx.comment.YcOnDestroy;
import com.yc.yclibx.file.YcFileUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;

/**
 *
 */
public class AudioRecordHelper implements YcOnDestroy {
    /**
     * DEFAULT = 0;             默认声音
     * MIC = 1;                 麦克风声音
     * VOICE_UPLINK = 2;        通话上行声音
     * VOICE_DOWNLINK = 3;      通话下行声音
     * VOICE_CALL = 4;          通话上下行声音
     * CAMCORDER = 5;           根据摄像头转向选择麦克风
     * VOICE_RECOGNITION = 6;   对麦克风声音进行声音识别，然后进行录制
     * VOICE_COMMUNICATION = 7; 对麦克风中类似ip通话的交流声音进行识别，默认会开启回声消除和自动增益
     * REMOTE_SUBMIX = 8;       录制系统内置声音
     */
    //音频输入-麦克风
    private int audioSource = MediaRecorder.AudioSource.VOICE_COMMUNICATION;
    //采用频率
    //44100是目前的标准，但是某些设备仍然支持22050，16000，11025
    //采样频率一般共分为22.05KHz、44.1KHz、48KHz三个等级
    private int sampleRateInHz = 44100;

//    private int audioChannel = AudioFormat.CHANNEL_CONFIGURATION_MONO;
    //    //声道 单声道
    private int audioChannel = AudioFormat.CHANNEL_IN_MONO;

    //声道 立体声道
//    private int audioChannel = AudioFormat.CHANNEL_IN_STEREO;
    //编码制式和采样大小:：采集来的数据当然使用PCM编码(脉冲代码调制编码，即PCM编码。PCM通过抽样、量化、编码三个步骤将连续变化的模拟信号转换为数字编码。)
    // android支持的采样大小16bit 或者8bit。当然采样大小越大，那么信息量越多，音质也越高，现在主流的采样大小都是16bit，在低质量的语音传输的时候8bit足够了。
    private int audioFormat = AudioFormat.ENCODING_PCM_16BIT;

    // 缓冲区字节大小
    private int bufferSizeInBytes = 0;

    //录音对象
    private AudioRecord audioRecord;
    private Disposable mDisposable;
    private String saveFilePathPcm = YcFileUtils.SD_PATH + File.separator + "test2.pcm";
    private String saveFilePathWav = YcFileUtils.SD_PATH + File.separator + "test2.wav";
    private boolean isRecording = false;//是否在录音

    public AudioRecordHelper(YcManage ycManage) {
        ycManage.add(this);
    }

    public void start() {
        createThread();
    }

    private void createAudioRecord() {
        if (audioRecord == null) {
            // 获得缓冲区字节大小
            //设置线程优先级，android.os.Process.THREAD_PRIORITY_AUDIO-标准音乐播放使用的线程优先级
            android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);
            bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, audioChannel, audioFormat);
            audioRecord = new AudioRecord(audioSource, sampleRateInHz, audioChannel, audioFormat, bufferSizeInBytes);
        }
    }

    private void createThread() {
        if (mDisposable != null || isRecording) {
            YcLog.e("请先关掉录音，再重新开启录音!!!!");
            return;
        }
        mDisposable = Observable.just(1)
                .subscribeOn(Schedulers.newThread())//上游
//                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<Integer>() {
                    @Override
                    public void accept(Integer integer) throws Exception {
                        createAudioRecord();
                        audioRecord.startRecording();
                        isRecording = true;
                        startWrite();
                        pcmToWav(saveFilePathPcm, saveFilePathWav);//给裸数据加上头文件
                    }
                });
    }


    private void startWrite() {
        YcFileUtils.delFiel(saveFilePathPcm);
        File file = YcFileUtils.createFile(saveFilePathPcm);
        if (file.exists()) {
            file.delete();
        }
        try (FileOutputStream fos = new FileOutputStream(file)) {// 建立一个可存取字节的文件
            int readSize;
            // new一个byte数组用来存一些字节数据，大小为缓冲区大小
            byte[] audioData = new byte[bufferSizeInBytes];
            while (isRecording) {
                readSize = audioRecord.read(audioData, 0, bufferSizeInBytes);
                if (AudioRecord.ERROR_INVALID_OPERATION != readSize) {
                    fos.write(audioData);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        mDisposable.dispose();
    }

    // 这里得到可播放的音频文件
    private void pcmToWav(String inFilename, String outFilename) {
        FileInputStream in;
        FileOutputStream out;
        long totalAudioLen;
        long totalDataLen;
        YcFileUtils.delFiel(outFilename);
        YcFileUtils.createFile(outFilename);
        int channels = audioChannel == AudioFormat.CHANNEL_IN_MONO ? 1 : 2;
        long byteRate = 16 * sampleRateInHz * channels / 8;
        byte[] data = new byte[bufferSizeInBytes];
        try {
            in = new FileInputStream(inFilename);
            out = new FileOutputStream(outFilename);
            totalAudioLen = in.getChannel().size();
            totalDataLen = totalAudioLen + 36;
            WriteWaveFileHeader(out, totalAudioLen, totalDataLen, sampleRateInHz, channels, byteRate);
            while (in.read(data) != -1) {
                out.write(data);
            }
            in.close();
            out.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 这里提供一个头信息。插入这些信息就可以得到可以播放的文件。
     * 为啥插入这44个字节，这个还真没深入研究，不过你随便打开一个wav
     * 音频的文件，可以发现前面的头文件可以说基本一样哦。每种格式的文件都有
     * 自己特有的头文件。
     */
    private void WriteWaveFileHeader(FileOutputStream out, long totalAudioLen, long totalDataLen, long longSampleRate, int channels, long byteRate) throws IOException {
        byte[] header = new byte[44];
        header[0] = 'R'; // RIFF/WAVE header
        header[1] = 'I';
        header[2] = 'F';
        header[3] = 'F';
        header[4] = (byte) (totalDataLen & 0xff);
        header[5] = (byte) ((totalDataLen >> 8) & 0xff);
        header[6] = (byte) ((totalDataLen >> 16) & 0xff);
        header[7] = (byte) ((totalDataLen >> 24) & 0xff);
        header[8] = 'W';
        header[9] = 'A';
        header[10] = 'V';
        header[11] = 'E';
        header[12] = 'f'; // 'fmt ' chunk
        header[13] = 'm';
        header[14] = 't';
        header[15] = ' ';
        header[16] = 16; // 4 bytes: size of 'fmt ' chunk
        header[17] = 0;
        header[18] = 0;
        header[19] = 0;
        header[20] = 1; // format = 1
        header[21] = 0;
        header[22] = (byte) channels;
        header[23] = 0;
        header[24] = (byte) (longSampleRate & 0xff);
        header[25] = (byte) ((longSampleRate >> 8) & 0xff);
        header[26] = (byte) ((longSampleRate >> 16) & 0xff);
        header[27] = (byte) ((longSampleRate >> 24) & 0xff);
        header[28] = (byte) (byteRate & 0xff);
        header[29] = (byte) ((byteRate >> 8) & 0xff);
        header[30] = (byte) ((byteRate >> 16) & 0xff);
        header[31] = (byte) ((byteRate >> 24) & 0xff);
        header[32] = (byte) (2 * 16 / 8); // block align
        header[33] = 0;
        header[34] = 16; // bits per sample
        header[35] = 0;
        header[36] = 'd';
        header[37] = 'a';
        header[38] = 't';
        header[39] = 'a';
        header[40] = (byte) (totalAudioLen & 0xff);
        header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
        header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
        header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
        out.write(header, 0, 44);
    }

    public void stopRecord() {//停止录音
        isRecording = false;
        // 释放资源
        if (null != audioRecord) {
            audioRecord.stop();
            audioRecord.release();
            audioRecord = null;
            mDisposable.dispose();
            //recordingThread = null;
        }
    }

    @Override
    public void onDestroy() {
        stopRecord();
        if (mDisposable != null) {
            mDisposable.dispose();
        }
        if (isRecording) {
            stopRecord();
        }
    }
}
